<html>
    <head>
        <title>数据传输对象  Dto</title>
    </head>
    <body>
        <script>
            // 数据传输对象 Dto  

                    /*

                        一、导言

                                    数据传输对象（DTO）用于在"应用层"和"表示层"或 "其他类型的客户端" 之间传输数据。

                                    通常，使用DTO(输入Dto)作为参数"从表示层"（可选）调用 "应用程序服务"。

                                    "应用服务层"使用领域对象执行一些特定的业务逻辑并（可选）将DTO(输出Dto)返回给表示层。

                                    因此，表示层与域层完全隔离。


                                    1、为什么需要Dto？

                                            如果你认为你知道并确认使用DTO的好处，你可以跳过本节。

                                            （1）、领域对象的抽象

                                                        DTO提供了一种从表示层中抽象域对象的有效方法。

                                                        实际上，你的层是正确分离的。如果你想完全更改表示层，你可以继续使用现有的应用程序层和域层。

                                                        或者，你可以重新编写你的领域层，完全更改数据库模式、实体和O/RM框架，所有这些都 "无需更改表示层".

                                                        当然，只要你的应用程序服务的契约（方法签名和DTO）保持不变
                                                        
                                            (2)、数据隐藏

                                                        假设你有一个User实体，属性为Id、Name、EmailAddress和Password。

                                                        如果UserAppService的GetAllUsers()方法返回一个List<User>，

                                                        任何人都可以访问你所有用户的密码，即使你没有在屏幕上显示它。

                                                        这不仅仅是关于安全性，而是关于数据隐藏。用程序服务应该只返回表示层（或客户端）需要的内容。不多不少。

                                            (3)、序列化和延迟加载问题

                                                        当你将数据（对象）返回到表示层时，它很可能是序列化的。

                                                        例如，在返回JSON的REST API中，你的对象将被序列化为JSON并发送到客户端。

                                                        在这方面，将实体返回到表示层可能会有问题，特别是如果你使用关系数据库和像实体框架核心这样的ORM提供程序。怎么做？

                                                        在现实世界的应用程序中，你的实体可能具有对彼此的引用。User实体可以引用它的Roles。如果你想序列化User，它的Roles也会序列化。

                                                        Role类可能有一个List<Permission>，Permission类可以引用PermissionGroup类等等……想象一下所有这些对象都被同时序列化。

                                                        你可能很容易意外地序列化你的整个数据库！此外，如果你的对象有循环引用，它们可能根本不会被序列化。

                                                        解决方案是什么？将属性标记为NonSerialized？

                                                        不，你无法知道何时应该序列化，何时不应该序列化。在一种应用程序服务方法中可能需要它，而在另一种应用程序服务方法中不需要它。

                                                        在这种情况下，返回安全、可序列化和专门设计的DTO是一个不错的选择。



                                                        几乎所有的O/RM框架都支持延迟加载。这是一个在需要时从数据库中加载实体的功能。

                                                        假设一个User类引用了一个Role类。当你从数据库中获得一个User时，Role属性（或集合）没有被填充。

                                                        当你第一次读取Role属性时，它是从数据库中加载的。
                                                        
                                                        因此，如果你将这样一个实体返回到表示层，它将导致它通过执行额外的查询从数据库中检索额外的实体。

                                                        如果序列化工具读取实体，它会递归地读取所有属性，并且可以再次检索你的整个数据库（如果实体之间存在关系）。


                                                        如果你在表示层中使用实体，可能会出现更多问题。最好不要在表示层中引用域/业务层程序集。

                                                        如果你确信使用DTO，我们可以继续ABP提供的关于DTO的建议。
                    */ 




                    /*
                        二、DTO 的 "标准接口" 和 "基类"

                                        DTO是一个简单的类，没有依赖关系，你可以以任何方式设计它。

                                        然而，ABP引入了一些接口来明确 标准属性、基类 的命名约定，这样你可以不要写重复代码。

                                        它们都"不是必需的"，但使用它们可以 "简化" 和 "标准化" 你的应用程序代码。


                                        1、实体相关的Dto
                                              
                                                        (1). EntityDto 

                                                                IEntityDto<TKey>是一个简单的接口，仅定义了Id属性。
                                                                    
                                                                你可以为与实体匹配的DTO实现它或从EntityDto<TKey>继承。
                                                                    
                                                                    eg：

                                                                        using System;
                                                                        using Volo.Abp.Application.Dtos;

                                                                        namespace AbpDemo
                                                                        {
                                                                            public class ProductDto : EntityDto<Guid>
                                                                            {
                                                                                public string Name { get; set; }
                                                                                //...
                                                                            }
                                                                        }



                                                        (2)、会被审计的Dto
                                                                    
                                                                如果你的实体继承自"已审计的实体类"（或实现审计接口），你可以使用以下基类来创建DTO：

                                                                        ①、CreationAuditedEntityDto

                                                                        ②、CreationAuditedEntityWithUserDto

                                                                        ③、AuditedEntityDto

                                                                        ③、AuditedEntityWithUserDto

                                                                        ④、FullAuditedEntityDto

                                                                        ⑤、FullAuditedEntityWithUserDto



                                                        (3)可扩展 Dto
                                                                
                                                                        如果要为DTO使用对象扩展系统，可以使用或继承以下DTO类：

                                                                                ①、ExtensibleObject实现了IHasExtraProperties（其他基类继承了这个类）。

                                                                                ②、ExtensibleEntityDto 

                                                                                ③、ExtensibleCreationAuditedEntityDto

                                                                                ④、ExtensibleCreationAuditedEntityWithUserDto

                                                                                ⑤、ExtensibleAuditedEntityDto

                                                                                ⑥、ExtensibleAuditedEntityWithUserDto

                                                                                ⑦、ExtensibleFullAuditedEntityDto

                                                                                ⑧、ExtensibleFullAuditedEntityWithUserDto



                                        2、列表结果 List Result

                                                      向客户端返回DTO列表是常见的。IListResult<T>接口和ListResultDto<T>类用于使其成为标准。
                                                        
                                                      IListResult<T>接口的定义：

                                                                public interface IListResult<T>
                                                                {
                                                                    IReadOnlyList<T> Items { get; set; }
                                                                }

                                                      eg: 返回产品列表

                                                                using System;
                                                                using System.Collections.Generic;
                                                                using System.Threading.Tasks;
                                                                using Volo.Abp.Application.Dtos;
                                                                using Volo.Abp.Application.Services;
                                                                using Volo.Abp.Domain.Repositories;

                                                                namespace AbpDemo
                                                                {
                                                                    public class ProductAppService : ApplicationService, IProductAppService
                                                                    {
                                                                        private readonly IRepository<Product, Guid> _productRepository;

                                                                        public ProductAppService(IRepository<Product, Guid> productRepository)
                                                                        {
                                                                            _productRepository = productRepository;
                                                                        }

                                                                        public async Task<ListResultDto<ProductDto>> GetListAsync()
                                                                        {
                                                                            // 货物产品列表
                                                                            List<Product> products = await _productRepository.GetListAsync();

                                                                            // 映射到Dto
                                                                            List<ProductDto> productDtos =
                                                                                ObjectMapper.Map<List<Product>, List<ProductDto>>(products);

                                                                            //  返回列表结果对象
                                                                            return new ListResultDto<ProductDto>(productDtos);
                                                                        }
                                                                    }
                                                                }

                                                       你可以简单地返回productDtos对象（并更改方法返回类型），它没有任何问题。

                                                       返回ListResultDto会使你的List<ProductDto>作为Items属性包装到另一个对象中。

                                                       这有一个优点：你可以稍后在返回值中添加更多属性，而不会破坏你的远程客户端（当他们以JSON结果获取值时）。




                                        3、分页 & 排序 列表结果  Paged & Sorted List Results

                                                       从服务器请求分页列表并将分页列表返回给客户端更常见。ABP定义了一些接口和类来标准化它：

                                                       （1）、请求的输入类型  Input Type for Request

                                                                    以下接口和类用于标准化客户端发送的输入：

                                                                        ①、ILimitedResultRequest：定义MaxResultCount（int）属性以从服务器请求有限的结果。

                                                                        ②、IPagedResultRequest：从ILimitedResultRequest继承（因此它固有地具有MaxResultCount属性）
                                                                        
                                                                                                并定义一个SkipCount（int）来声明跳过计数，同时从服务器请求分页结果。

                                                                        ③、ISortedResultRequest：定义一个Sorting（string）属性，用于从服务器请求排序结果。
                                                                        
                                                                                                排序值可以是“Name”、“Name DESC”、“Name ASC， age DESC”…等等。

                                                                        ④、IPagedAndSortedResultRequest

                                                                                                    继承自IPagedResultRequest和ISortedResultRequest，
                                                                                                    
                                                                                                    因此具有MaxResultCount、SkipCount和Sorting属性。


                                                                        ★、 建议不要手动实现接口，而是继承以下基本DTO类之一：

                                                                            ①、LimitedResultRequestDto实现ILimitedResultRequest。

                                                                            ②、PagedResultRequestDto实现IPagedResultRequest（并继承自LimitedResultRequestDto）。

                                                                            ③、PagedAndSortedResultRequestDto实现IPagedAndSortedResultRequest（并继承自PagedResultRequestDto）。


                                                                         ★、最大结果数量  Max Result Count 

                                                                                LimitedResultRequestDto（以及固有的其他）通过以下规则限制和验证MaxResultCount：

                                                                                    ①、如果客户端未设置MaxResultCount，则假定为10（默认页面大小）。
                                                                                    
                                                                                            可以通过设置LimitedResultRequestDto.DefaultMaxResultCount "静态属性"。

                                                                                            如果客户端发送MaxResultCount大于1,000，则会产生验证错误。保护服务器免受服务滥用非常重要。

                                                                                            如果需要，可以通过设置LimitedResultRequestDto.MaxMaxResultCount静态属性来更改此值。


                                                                                        建议在应用程序启动时设置静态属性，因为它们是静态的（全局的）。

                                                    (2)、输出类型  Output Type for Response

                                                                以下接口和类用于标准化发送给客户端的输出:

                                                                      ①、IHasTotalCount定义了一个TotalCount（long）属性，用于在分页的情况下返回记录的总计数。

                                                                      ②、IPagedResult<T>继承自IListResult<T>和IHasTotalCount，因此它具有Items和TotalCount属性。


                                                                建议不要手动实现接口，而是继承以下基本DTO类之一：

                                                                      ①、PagedResultDto<T>继承自ListResultDto<T>，还实现了IPagedResult<T>。
                                                                

                                                                eg ：从服务器请求分页和排序的结果并返回分页列表

                                                                        using System;
                                                                        using System.Collections.Generic;
                                                                        using System.Linq;
                                                                        using System.Linq.Dynamic.Core;
                                                                        using System.Threading.Tasks;
                                                                        using Microsoft.EntityFrameworkCore;
                                                                        using Volo.Abp.Application.Dtos;
                                                                        using Volo.Abp.Application.Services;
                                                                        using Volo.Abp.Domain.Repositories;

                                                                        namespace AbpDemo
                                                                        {
                                                                            public class ProductAppService : ApplicationService, IProductAppService
                                                                            {
                                                                                private readonly IRepository<Product, Guid> _productRepository;

                                                                                public ProductAppService(IRepository<Product, Guid> productRepository)
                                                                                {
                                                                                    _productRepository = productRepository;
                                                                                }

                                                                                // 获取分页和排序列表  返回分页排序列表结果类型
                                                                                public async Task<PagedResultDto<ProductDto>> GetListAsync(
                                                                                    PagedAndSortedResultRequestDto input)
                                                                                {
                                                                                    // 创建查询
                                                                                    var query = _productRepository
                                                                                        .OrderBy(input.Sorting);

                                                                                    // 查询总记录数
                                                                                    var totalCount = await query.CountAsync();
                                                                                    
                                                                                    // 获取实体列表
                                                                                    List<Product> products = await query                
                                                                                        .Skip(input.SkipCount)
                                                                                        .Take(input.MaxResultCount).ToListAsync();

                                                                                    // 列表进行对象映射成DTO
                                                                                    List<ProductDto> productDtos =
                                                                                        ObjectMapper.Map<List<Product>, List<ProductDto>>(products);

                                                                                    // 返回分页排序列表结果
                                                                                    return new PagedResultDto<ProductDto>(totalCount, productDtos);
                                                                                }
                                                                            }
                                                                        }


                                                     ABP还定义了一个PageBy扩展方法（与IPagedResultRequest兼容），可以用来代替Skip+Take调用：

                                                                var query = _productRepository
                                                                                            .OrderBy(input.Sorting)
                                                                                            .PageBy(input);
                                                                        

                                                    备注：

                                                            请注意，我们将 "Volo.Abp.EntityFrameworkCore" NuGet包添加到项目中，
                                                            
                                                            以便能够使用ToListAsync和CountAsync方法，因为它们不包含在标准LINQ中，而是由Entity Framework Core定义。
                    */

                    /*
                        三、其他相关主题

                                1、验证  Validation
                                                                                        
                                            应用程序服务方法、控制器操作、页面模型输入…的输入会自动验证。
                                            
                                            你可以使用标准"数据注释"属性或 "自定义验证方法" 来执行验证。

                                            有关更多信息，请参阅验证文档:  https://abp.io/docs/latest/framework/fundamentals/validation


                                 2、对象映射

                                            当你创建与实体相关的DTO时，通常需要映射这些对象。ABP提供了一个对象到对象的映射系统来简化映射过程。
                    */

                    /*
                        四、最佳实践

                                 你可以自由设计你的DTO类。但是，你可能需要遵循一些最佳实践和建议。


                                 1、通用原则

                                            DTO应该是可很好地序列化的，因为它们通常是序列化和反序列化的（JSON或其他格式）。

                                            如果你有另一个带有参数的构造函数，建议创建一个空的（无参数的）公共构造函数。


                                            DTO不应包含任何业务逻辑，除了一些正式的验证代码。(Dto要保持纯净)

                                            不要从实体派生DTO，并且不引用实体。应用程序启动模板已经通过分隔项目来防止它。

                                            如果你使用自动对象到对象映射库，例如AutoMapper，请启用映射配置验证以防止潜在错误。


                                2、输入Dto原则  Input DTO Principles 

                                            仅定义用例所需的属性。不要包含不用于用例的属性，如果你这样做会使开发人员感到困惑。

                                            不要在不同的应用程序服务方法之间重用输入DTO。

                                            因为，不同的用例需要和使用不同的DTO属性，这导致某些情况下某些属性没有使用，这使得更难理解和使用服务，并在未来导致潜在的错误。

                    */
        </script>
    </body>
</html>